Skip to content

Added [System.Flags] to enums that are flags. - #354

Open
twindan wants to merge 8 commits into
Ruslan-B:mainfrom
twindan:add_flags_to_enums
Open

Added [System.Flags] to enums that are flags.#354
twindan wants to merge 8 commits into
Ruslan-B:mainfrom
twindan:add_flags_to_enums

Conversation

@twindan

@twindan twindan commented Jun 12, 2026

Copy link
Copy Markdown

Description

Some enums that are conceptually flags (e.g., SwsFlags) do no have [System.Flags] on the enum definition. This code adds that definition for the relevant classes.

What I do is look at the enum definition in ffmpeg when parsing. If it is of the form "1 << n", then I preserve the definition instead of collapsing the value (so the generated file say "VALUE = 1 << 3" instead of "VALUE = 8"). If all items in an enum are of the form 1 << n, then I treat it as a [System.Flags]. This seems to be how ffmpeg uses enumerated values.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code generation update
  • Dependency update

Related Issues

#353

Testing

Ran the output

  • I have tested my changes locally
  • I have run dotnet build -c Release successfully
  • I have run dotnet test -c Release successfully
  • [N/A] I have tested with the example project if applicable

Checklist

  • My code follows the project's code style
  • [N/A] I have updated the documentation if needed
  • [N/A] I have added tests for new functionality if applicable
  • All existing tests pass
  • I have checked that my changes don't introduce new warnings

Additional Notes

Output is the same except for enums that now have [System.Flags] appended. I have included the new generated files in this PR. Ran against ffmpeg commit 9047fa1b.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the unsafe generator and regenerated enum outputs so that enums whose members are all expressed as single-bit shifts (1 << n) are emitted as C# flags enums ([Flags]) and preserve the shift-form instead of collapsing to numeric literals.

Changes:

  • Updated enum processing to preserve = 1 << n expressions in generated enum member values (with appropriate numeric literal suffixes).
  • Updated enum generation to emit using System; and add [Flags] to enums where every member value contains a left-shift.
  • Regenerated Enums.g.cs outputs to reflect [Flags] and 1 << n formatting for the affected enums (e.g., SwsFlags, AVTimecodeFlag).

Reviewed changes

Copilot reviewed 2 out of 4 changed files in this pull request and generated no comments.

File Description
FFmpeg.AutoGen/generated/Enums.g.cs Regenerated enums now include using System;, [Flags] on identified flags enums, and 1 << n member values.
FFmpeg.AutoGen.CppSharpUnsafeGenerator/Processing/EnumerationProcessor.cs Preserves = 1 << n expressions during enum item value conversion via a generated regex.
FFmpeg.AutoGen.CppSharpUnsafeGenerator/Generation/EnumsGenerator.cs Emits using System; and adds [Flags] when all enum items contain <<.
FFmpeg.AutoGen.Abstractions/generated/Enums.g.cs Same regeneration as main package enums: using System;, [Flags], and 1 << n values where applicable.

twindan and others added 3 commits August 21, 2026 20:22
# Conflicts:
#	FFmpeg.AutoGen.Abstractions/generated/Enums.g.cs
#	FFmpeg.AutoGen/generated/Enums.g.cs
The detection keyed on the header writing a member as "1 << n", which makes
the result depend on FFmpeg's source formatting rather than on what the enum
means. SWS_BACKEND_C is written "(1 << 1)" and the regex could not match the
closing parenthesis; AV_CODEC_HW_CONFIG_METHOD_* and
AV_SIDE_DATA_PARAM_CHANGE_* are written in hex. All are bitmasks by their own
header documentation and all were missed, and a reformatting upstream could
turn the attribute on or off with no change here.

EnumerationDefinition now carries IsFlags, computed in the processor from the
numeric values: at least two distinct single-bit members, at most a couple of
named aggregates such as SWS_BACKEND_ALL, every aggregate covered by bits the
enum introduces, sentinels like *_MAX_ENUM ignored, and a consecutive run
rejected so that 0, 1, 2 stays a sequence. EnumsGenerator reads that flag
instead of sniffing for "<<" in the emitted text.

Marks four more: AvCodecHwConfigMethod, AVSideDataParamChangeFlags,
AVSideDataProps and SwsBackend. Drops one: AvFrameCrop has a single member, and
values alone cannot tell AV_FRAME_CROP_UNALIGNED from
AV_HWFRAME_TRANSFER_DIRECTION_FROM/TO, which have the same shape and are a
direction rather than a mask.

Keeping the shift spelling is now a separate concern from detection, and it is
verified: the literal is only kept when 1 << n actually reproduces the value
the compiler computed, since C# masks shift counts and would otherwise emit a
different constant. The suffix comes from the type name the enum is generated
with rather than from a second table of primitives, which had no cases for
LongLong and ULongLong - the two that really are C# long and ulong - and
disagreed with TypeHelper about Long and ULong.

EnumerationItem keeps a numeric Value and gains Literal. Value is what
ASTProcessor publishes into WellKnownEnumItems, where it is spliced into macro
bodies as text, so an unparenthesised "1 << 3" there would have changed the
precedence of any macro referring to the member.

Also: invariant culture when formatting values, since several locales render
the negative sign as U+2212 and the generated file is C# source; a named regex
group per CONTRIBUTING; and "using System;" only when something is actually
marked.

Co-authored-by: Daniel Sass <twindan@users.noreply.github.com>
@Ruslan-B

Copy link
Copy Markdown
Owner

Thanks for this, and sorry it sat for so long. Two things I want to say before the changes: you filed the issue, implemented it, and then came back and rebased onto 9.0.1.1 the same day it shipped. And the generated files in the branch reproduce byte-for-byte from your generator — I checked with git diff --exit-code -- '*.g.cs' after a clean regeneration. That is rarer than it should be, and it made this reviewable in minutes.

I have pushed a commit onto your branch rather than sending you round another lap. Look it over and push back on anything you disagree with.

What I changed and why

The detection keyed on the header spelling a member as 1 << n. That makes the outcome depend on how FFmpeg formatted its source rather than on what the enum means, and it misses real bitmasks:

SWS_BACKEND_C = (1 << 1),                        // parenthesised - the regex is anchored to $
AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX = 0x01,  // hex
AV_SIDE_DATA_PARAM_CHANGE_SAMPLE_RATE = 0x0004,  // hex, and the header's own docs use it as a mask

All three are documented bitmasks. The sharper worry is that this can flip with no change on our side: if upstream ever reformats 1 << 3 to (1 << 3), the attribute silently disappears.

So IsFlags now lives on EnumerationDefinition and is computed in the processor from the numeric values: at least two distinct single-bit members, at most a couple of named aggregates like SWS_BACKEND_ALL, every aggregate covered by bits the enum introduces, *_MAX_ENUM = INT_MAX sentinels ignored, and a consecutive run rejected so 0, 1, 2 stays a sequence. EnumsGenerator reads that instead of looking for << in the text it is about to print.

Net effect, 5 enums marked before, 8 now:

added AvCodecHwConfigMethod, AVSideDataParamChangeFlags, AVSideDataProps, SwsBackend
dropped AvFrameCrop

AvFrameCrop is an honest loss and worth flagging: it has a single member, and by values alone AV_FRAME_CROP_UNALIGNED is indistinguishable from AV_HWFRAME_TRANSFER_DIRECTION_FROM/TO, which is a direction, not a mask. If we want the single-bit ones (AvFrameCrop, AVClassStateFlags, AvOptFlagImplicit) an explicit list is probably more honest than another heuristic.

Keeping the shift spelling

I kept it — reading like the header is genuinely useful when you are porting C code across — but it is now independent of detection and verified, because C# masks shift counts. X = 1 << 40 in a 32-bit enum compiles as 1 << 8, so the literal is only kept when 1UL << n actually equals the value clang computed; otherwise it falls back to the number. It also now matches the parenthesised form, which is why more members render as shifts than before.

The suffix comes from the type name the enum is generated with, rather than a second PrimitiveType table. That table had no cases for LongLong/ULongLong — the two that really are C# long/ulong — and disagreed with TypeHelper about Long/ULong, which map to CLong/CULong since #359.

Smaller things

  • EnumerationItem keeps a numeric Value and gains Literal. ASTProcessor publishes Value into WellKnownEnumItems, where it is spliced into macro bodies as text — an unparenthesised 1 << 3 there would have changed the precedence of any macro referencing the member. It does not fire on today's headers, but the coupling was there.
  • ConvertValue now formats with InvariantCulture. Enums.g.cs has negative members, and on sv-SE, fi-FI, nb-NO and lt-LT (-1).ToString() returns U+2212 MINUS SIGN, which does not compile. That one predates your PR, it just happened to be on the line you were editing.
  • Named regex group instead of Groups[1], matching what the generator does elsewhere.
  • using System; only when something is actually marked.

One question for you

swscale.h says of SWS_FAST_BILINEAR..SWS_SPLINE: "Scaler selection options. Only one may be active at a time." So SwsFlags is really a selector in its low bits and a flag set in its high ones, and [Flags] blesses SWS_BILINEAR | SWS_BICUBIC, which the header forbids. #353 asked for the attribute and I have left it on, but you have looked at this enum more recently than I have — if you think it should be split or left alone, say so.

Worth knowing for scale, by the way: FFmpeg keeps almost all of its flags in macros, not enums — 149 #define ..._FLAG... and 195 #define X (1 << n) against 8 flag-shaped enums. Those become plain constants in ffmpeg.macros.g.cs, so no attribute can reach them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants